﻿using Cheng;
using DG.Tweening;
using GameFramework.Event;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#pragma warning disable 0649
[RequireComponent(typeof(BoxCollider2D), typeof(Rigidbody2D))]
public class CharacterController2D : MonoBehaviour
{


    public struct CharacterColState2D
    {
        public bool Right;
        public bool Left;
        public bool Top;
        public bool Bottom;


        public bool IsColision
        {
            get { return Right || Left || Top || Bottom; }
        }
        public void Reset()
        {
            Right = Left = Top = Bottom = false;
        }
    }

    [SerializeField]
    private float _skin = 0.1f;//皮肤的厚度，一般定义0.001-0.1，建议在0.00级别。
    [SerializeField]

    private int _hraycount;//在x轴方向射线个数，我们定义为4个，同时y轴方向的射线个数也是4个

    [SerializeField]
    private int _vraycount;
    [SerializeField]
    private LayerMask _obstacleMask;//1号障碍物的层标识，default的层数字为1，所在的实际层为2<<层数
    [SerializeField]
    private LayerMask _onewayPlatforms;//2号障碍物的层标识
    [SerializeField]
    private float _slopeLimit;//玩家所能爬的最陡坡度


    public AnimationCurve slopeSpeedMultiplier = new AnimationCurve(new Keyframe(0f, 1f), new Keyframe(90f, 0f));


    private BoxCollider2D _boxCollider2D;
    private CharacterColState2D _colState2D;//碰撞信息，分别存储了4个方向的碰撞信息，分别为top，bottom，left，right


    private Vector3 _bottomright;//底边射点的右起始点，后面的点加上一点的偏移量即可求得，下面依次类推
    private Vector3 _bottomleft;
    private Vector3 _topright;
    private Vector3 _topleft;


    private LayerMask _curMask;//最后玩家实际需要检测的层


    public bool IgnoreOneWayPlatforms;//表示是否忽略OneWayPlatforms
    [Min(0)]
    public float RayDistance = float.NaN;
    private bool _lastFramIsGround;

    private Vector3 normal;
    private Vector3 jump = new Vector3(1, 1);
    private Vector3 jumpostion;
    private Vector3 normalX;
    public bool IsGround { get { return _colState2D.Bottom; } }
    //private Timer jumpTimer;

    private Transform body;
    private void Awake()
    {
        body = transform.FindChild_ByTag("Body");

        GameEntry.Event?.Subscribe(CustomBaseEventArgs.EventId, (object sender, GameEventArgs e) =>
        {
            CustomBaseEventArgs cbe = (CustomBaseEventArgs)e;
            if (cbe.EventName == "NormalWalk")
            {
                //Debug.LogWarning(((Vector2)cbe._Values[0]).normalized);

                Move((Vector2)cbe._Values[0]);
            }

        });

        normalX = new Vector3(transform.localScale.x, 0, 0);
        normal = new Vector3(0, transform.localScale.y, transform.localScale.z);
        _boxCollider2D = this.transform.GetComponent<BoxCollider2D>();//先缓存一下玩家的盒子碰撞器
        GetRayOffset();
    }
    public void Move(Vector3 delta, bool Adjustdirection = true)//该方法为该组件对外的方法。同时也是主要方法。如果当前帧没碰到碰撞器，那下帧情况我们不是很清楚，所以我们的做法就是每帧都去检测，同样每帧的碰撞信息都应该清除
    {
        if (DOTween.IsTweening(transform))
        {
            return;
        }
        _curMask = _obstacleMask;

        _lastFramIsGround = IsGround;//判断上一帧玩家是不是在地上，因为没帧都在检测，所以我们需要保存上一帧的玩家碰撞信息。
        _colState2D.Reset();//碰撞信息重新清除

        RecalculateRayOrigin();//重新计算射线其实点，每帧玩家的位置信息都在发生变化，所以每帧都需重新计算。
        if (Mathf.Abs(delta.x) > 0.001f)//如果在x方向偏移不为0，对其进行计算，如果y轴上的偏移不为0同样也需对其进行计算。这2个方法的作用就是返回一个偏移通过delta传出去，ref 和 out表示值类型的参数按引用传递，本质都是传的地址。区别我就不说了。
            MoveHorizontal(ref delta, Adjustdirection);


        if (Mathf.Abs(delta.y) > 0.001f)
            MoveVertical(ref delta);
        //if(_colState2D.IsColision)
        //{
        //    delta = Vector3.zero;
        //}
        delta.z = 0;
        //this.transform.Translate(delta, Space.Self);
        transform.DOMove(delta+ transform.position, 0.3f);
        IgnoreOneWayPlatforms = false;
    }
    private void MoveHorizontal(ref Vector3 delta, bool Adjustdirection = true)
    {
        bool isrgiht = delta.x > 0;//判断是不是向右

        /*自定义*/
        if (Mathf.Abs(delta.x) > Mathf.Abs(delta.y))
        {
            for (int i = 0; i < transform.childCount - 1; i++)
            {
                transform.GetChild(i).gameObject.SetActive(false);
            }
            transform.GetChild(isrgiht ? 3 : 2).gameObject.SetActive(true);
        }

        /*自定义*/
        Vector2 raydir = isrgiht ? Vector2.right : -Vector2.right;//如果向右的话，那么射线的方向就是向右的。

        Vector2 rayorigin = isrgiht ? _bottomright : _bottomleft;//如果向右的话，起始点我们从右下边，反之就是左下边取。

        float rayDistance = Mathf.Abs(delta.x) + _skin; //射线的方向是偏移量在x轴上的分量加上皮肤的厚度。

        for (int i = 0; i < _hraycount; i++)
        {
            var originpos = new Vector2(rayorigin.x, rayorigin.y + i * _vrayoffset);//依次求出射点的位置。

            RaycastHit2D hit;
            hit = Physics2D.Raycast(originpos, raydir, rayDistance, _obstacleMask);
            Debug.DrawRay(originpos, raydir, Color.red);
            if (hit)
            {

                if (i == 0 && HandleHorizontalSlope(ref delta, Vector2.Angle(hit.normal, Vector2.up)))//如果玩家是在爬坡的话，并且只有0号射点才能检测到障碍物的话，那么我们不用计算下面的了。

                {
                    break;
                }
                delta.x = hit.point.x - originpos.x;
                if (isrgiht)//如果检测到了障碍物的话，并且是在右边话，表示玩家的朝向有一个障碍物

                {
                    delta.x -= _skin;
                    _colState2D.Right = true;
                }
                else
                {
                    delta.x += _skin;
                    _colState2D.Left = true;
                }
                break;
            }
        }
    }//该方法的作用是，假如我们离障碍物有0.1，如果我们下一帧移动偏移量为0.2的话，如果我们还是按照0.2进行移动话，那么玩家就和障碍物重叠了，如果这时我们发现重叠了，然后又把玩家移动-0.1的偏移的话，在短暂的时间内，一般玩家是可以看出来的，所以我们的做法应该在这一帧只移动0.1.。同时下面的方法都是一样的，只有上坡的时候检测可能有点区别。

    private void MoveVertical(ref Vector3 delta)
    {
        bool isdown = delta.y < 0;
        /*自定义*/
        if (Mathf.Abs(delta.x) < Mathf.Abs(delta.y))
        {
            for (int i = 0; i < transform.childCount - 1; i++)
            {
                transform.GetChild(i).gameObject.SetActive(false);
            }
            transform.GetChild(isdown ? 0 : 1).gameObject.SetActive(true);
        }
        /*自定义*/
        Vector2 raydir = isdown ? -Vector2.up : Vector2.up;
        Vector2 rayorigin = isdown ? _bottomleft : _topleft;
        float rayDistance = Mathf.Abs(delta.y) + _skin;
        if (isdown && !_lastFramIsGround)
            _curMask |= _onewayPlatforms;
        if (_lastFramIsGround && IgnoreOneWayPlatforms)
            _curMask &= ~_onewayPlatforms;
        for (int i = 0; i < _vraycount; i++)
        {
            var originpos = new Vector2(rayorigin.x + i * _hrayoffset, rayorigin.y);
            RaycastHit2D hit;
            hit = Physics2D.Raycast(originpos, raydir, rayDistance, _curMask);
            Debug.DrawRay(originpos, raydir, Color.red);
            if (hit)
            {

                delta.y = hit.point.y - originpos.y;
                if (isdown)
                {
                    delta.y += _skin;
                    _colState2D.Bottom = true;
                }
                else
                {
                    delta.y -= _skin;
                    _colState2D.Top = true;
                }
                break;
            }
        }
    }
    private bool HandleHorizontalSlope(ref Vector3 delta, float angle)
    {
        if (Mathf.RoundToInt(angle) == 90)
            return false;
        if (angle < _slopeLimit)
        {
            if (delta.y < 0.05f)
            {
                var slopeModifier = slopeSpeedMultiplier.Evaluate(angle);
                delta.x *= slopeModifier;
                delta.y = Mathf.Abs(Mathf.Tan(angle * Mathf.Deg2Rad) * delta.x);
                var isGoingRight = delta.x > 0;
                var ray = isGoingRight ? _bottomright : _bottomleft;
                RaycastHit2D raycastHit;
                //if (_lastFramIsGround)
                raycastHit = Physics2D.Raycast(ray, delta.normalized, delta.magnitude, _curMask);
                if (raycastHit)
                {
                    delta = (Vector3)raycastHit.point - ray;
                    if (isGoingRight)
                        delta.x -= _skin;
                    else
                        delta.x += _skin;
                }
                //_isGoingUpSlope = true;
                _colState2D.Bottom = true;
            }
        }
        return true;
    }
    private void RecalculateRayOrigin()//重新计算射线的各个位置的起始位置
    {

        float xoffset = _boxCollider2D.size.x * this.transform.localScale.x / 2;
        float yoofset = _boxCollider2D.size.y * this.transform.localScale.y / 2;
        //if(transform.childCount>0)
        //{
        //    _topleft = transform.GetChild(0).position + new Vector3(-xoffset + _skin, yoofset - _skin, 0);
        //    _bottomleft = transform.GetChild(0).position + new Vector3(-xoffset + _skin, -yoofset + _skin, 0);
        //    _topright = transform.GetChild(0).position + new Vector3(xoffset + -_skin, yoofset - _skin, 0);
        //    _bottomright = transform.GetChild(0).position + new Vector3(xoffset + -_skin, -yoofset + _skin, 0);
        //}
        //else
        {
            _topleft = (body == null ? transform.position : body.position) + new Vector3(-xoffset + _skin, yoofset - _skin, 0);
            _bottomleft = (body == null ? transform.position : body.position) + new Vector3(-xoffset + _skin, -yoofset + _skin, 0);
            _topright = (body == null ? transform.position : body.position) + new Vector3(xoffset + -_skin, yoofset - _skin, 0);
            _bottomright = (body == null ? transform.position : body.position) + new Vector3(xoffset + -_skin, -yoofset + _skin, 0);
        }

    }
    private float _hrayoffset;
    private float _vrayoffset;
    private void GetRayOffset()//计算每边射点与射点之间的距离。
    {
        var xdis = _boxCollider2D.size.x * this.transform.localScale.x - 2 * _skin;
        var ydis = _boxCollider2D.size.y * this.transform.localScale.y - 2 * _skin;
        _hrayoffset = xdis / _hraycount;
        _vrayoffset = ydis / _vraycount;
    }




}
